﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace Pfz.WpfControls
{
	/// <summary>
	/// Controls that can be bound to a full record.
	/// </summary>
	public class ObjectBoundControl<ObjectType>:
		BoundControl
	{
		internal WrapGrid _wrapGrid;
		internal StackPanel _stackPanel;
		
		/// <summary>
		/// Creates a new RecordBoundControl.
		/// </summary>
		public ObjectBoundControl()
		{
			_stackPanel = new StackPanel();
			Content = _stackPanel;
			_propertyBindings.CollectionChanged += propertyBindings_CollectionChanged;
            DataContextChanged += _DataContextChanged;
			Orientation = System.Windows.Controls.Orientation.Vertical;
		}

		/// <summary>
		/// Gets or sets the orientation of the internal controls.
		/// </summary>
		public Orientation Orientation
		{
			get
			{
				if (_stackPanel != null)
					return System.Windows.Controls.Orientation.Vertical;

				return System.Windows.Controls.Orientation.Horizontal;
			}
			set
			{
				if (value == Orientation)
					return;

				if (value == System.Windows.Controls.Orientation.Vertical)
				{
					_stackPanel = new StackPanel();
					Content = _stackPanel;
					_wrapGrid = null;
				}
				else
				{
					_wrapGrid = new WrapGrid();
					Content = _wrapGrid;
					_stackPanel = null;
				}
			}
		}

        private void _DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            ReadFromDataSource();
        }

		void propertyBindings_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			InvalidateContent();
		}

		/// <summary>
		/// Creates its content.
		/// </summary>
		protected override void OnCreateContent()
		{
			base.OnCreateContent();

			var children = Children;

			children.Clear();
			
			var propertyBindings = _propertyBindings;
			foreach(var propertyBinding in propertyBindings)
			{
				if (propertyBinding == null)
				{
					children.Add(new Separator());
					continue;
				}
				
				var control = new PropertyBoundControl<ObjectType>();
				control.PropertyBinding = propertyBinding;
				control.IsReadOnly = IsReadOnly;
				children.Add(control);
			}
			
			var args = new RoutedEventArgs(ObjectBoundControl.PropertyBoundControlsCreatedEvent);
			RaiseEvent(args);
		}
	
		/// <summary>
		/// Gets or sets the record to which this control is bound-to.
		/// </summary>
		[Browsable(false)]
		public ObjectType DataSource
		{
			get
			{
				object result = DataContext;
				if (result is ObjectType)
					return (ObjectType)result;
				
				return default(ObjectType);
			}
			set
			{
                DataContext = value;
			}
		}
		
		private ObservableCollection<PropertyBinding> _propertyBindings = new ObservableCollection<PropertyBinding>();
		
		/// <summary>
		/// Gets or sets the propeties this control will display.
		/// Example as string property:
		/// FullTypeName.* (which will get all properties)
		/// FullTypeName:PropertyName;OtherPropertyName
		/// FullTypeName:PropertyName=Display name;OtherPropertyName=Other display name
		/// FullTypeName.PropertyName=Display name;OtherFullTypeName.PropertyName=Other display name
		/// </summary>
		[TypeConverter(typeof(PropertyBindingsConverter))]
		public ObservableCollection<PropertyBinding> PropertyBindings
		{
			get
			{
				return _propertyBindings;
			}
			set
			{
				if (value == _propertyBindings)
					return;
					
				_propertyBindings.Clear();
				if (value != null)
					foreach(var item in value)
						_propertyBindings.Add(item);
			}
		}

		private IEnumerable<PropertyBinding> _AsBoundProperties(PropertyInfo[] propertyInfos)
		{
			foreach(var propertyInfo in propertyInfos)
				yield return new PropertyBinding { Property = propertyInfo };
		}

		/// <summary>
		/// Gets the Children used by this control.
		/// </summary>
		public UIElementCollection Children
		{
			get
			{
				if (_wrapGrid != null)
					return _wrapGrid.Children;

				return _stackPanel.Children;
			}
		}

		/// <summary>
		/// Refreshes (re-reads) the data in all internal controls.
		/// </summary>
		public void ReadFromDataSource()
		{
			_readFromDataSourceNeeded = false;
			
			bool isReadOnly = IsReadOnly;
			foreach(var control in Children)
			{
				var valueControl = control as PropertyBoundControl<ObjectType>;
				if (valueControl != null)
				{
					valueControl.IsReadOnly = isReadOnly;
					valueControl.ReadFromDataSource();
				}
			}
		}

		/// <summary>
		/// Calls ReadFromDataSourceNeeded.
		/// </summary>
		protected override void OnIsReadOnlyChanged()
		{
			ReadFromDataSourceNeeded();
		}

		private bool _readFromDataSourceNeeded;
		/// <summary>
		/// Tells that data-source must be re-read, but does not read it immediatelly.
		/// </summary>
		public void ReadFromDataSourceNeeded()
		{
			if (_readFromDataSourceNeeded)
				return;
				
			_readFromDataSourceNeeded = true;
			Dispatcher.Invoke(new Action(ReadFromDataSource));
		}
		
		/// <summary>
		/// Event invoked whenever the child-controls are (re-)created.
		/// </summary>
		public event RoutedEventHandler PropertyBoundControlsCreated
		{
			add
			{
				AddHandler(ObjectBoundControl.PropertyBoundControlsCreatedEvent, value);
			}
			remove
			{
				RemoveHandler(ObjectBoundControl.PropertyBoundControlsCreatedEvent, value);
			}
		}
	}
	
	/// <summary>
	/// Non-generic version of ObjectBoundControl. It's here because
	/// Xaml does not support generic-classes and because the events
	/// are better registered only once, as non-generic.
	/// </summary>
	public sealed class ObjectBoundControl:
		ObjectBoundControl<object>
	{
		/// <summary>
		/// Event invoked when the ObjectBoundControl finishes creating all
		/// it's property-bound controls.
		/// </summary>
		public static readonly RoutedEvent PropertyBoundControlsCreatedEvent =
			EventManager.RegisterRoutedEvent
			(
				"PropertyBoundControlsCreated",
				RoutingStrategy.Direct,
				typeof(RoutedEventHandler),
				typeof(ObjectBoundControl)
			);
	}
}
